#include "AILineSelector.h"
#include "../Controllers/DotController.h"
#include "../Models/DotModel.h"
#include "../Settings/GameSettings.h"
#include "../Utils/SequentialRandom.h"

class CellLinePair
{
public:
    CellLinePair(AILineSelector *pLineSelector,  Cell *cell, QLine line):mpLineSelector(pLineSelector), mpCell(cell),mLine(line)
    {
        mbLineDetached = false;
        mpLine = mpLineSelector->addLine(mLine);
    }
    Cell *cell(){return mpCell;}
    QLine line(){return mLine;}
    void detachLine()
    {
        if(!mbLineDetached){
            mbLineDetached = true;
            mpLineSelector->removeLine(mpLine);
        }
    }
private:
    AILineSelector *mpLineSelector;
    Cell *mpCell;
    Line *mpLine;
    QLine mLine;
    bool mbLineDetached;
};

AILineSelector::AILineSelector(DotController *pController,QObject *parent) :
    QObject(parent),mpController(pController),mnMinCellToFind(1),mbPreCalculated(false)
{
    connect(mpController,SIGNAL(gameStarted()),SLOT(gameStarted()));
}

QLine AILineSelector::selectLine()
{
    QList<Cell*> cellsToWinNow = mpController->model()->cellsWinableNow();

    if(cellsToWinNow.count() > 0){
        if(GameSettings::me()->setting(Game::PlayingLevel) == Game::Playing){
            return considerExtremeMode(cellsToWinNow);
        }
        else{
            return cellsToWinNow.at(0)->getSidesThatNotAddYet().at(0);
        }
    }

    QList<Cell*> cellsToWinLater = mpController->model()->cellsWinableLater();

    if(cellsToWinLater.count() > 0){
        sort(cellsToWinLater);
        QLine line = tryToFindAdjacentLine(cellsToWinLater);

        if(line.isNull()){            
            return tryToFindOptimizeLine(cellsToWinLater,mnMinCellToFind);
        }
        else{
            return line;
        }
    }

    return QLine();
}

void AILineSelector::winCellRecursively(Cell *pCell, QList<CellLinePair *> &cellLinePair)
{
    Cell *pNextCell = NULL;
    QLine winLine = mpController->model()->getWinLineAndNextCell(pCell,&pNextCell);

    if(!winLine.isNull()){
        cellLinePair.append(new CellLinePair(this,pCell,winLine));

        if(pNextCell && pNextCell->isWinableNow()){
            winCellRecursively(pNextCell,cellLinePair);
        }
    }
}

void AILineSelector::winableCellsInThisTurn(QList<Cell *> cellsOwnableNow,QList< QList< CellLinePair* > > &clp2d,uint &nTotal,uint &nMax)
{
    nTotal = 0;
    nMax = 0;

    do{
        for(int i = 0; i < cellsOwnableNow.count(); i++){
            Cell *pCell = cellsOwnableNow.at(i);

            if(pCell->isWinableNow()){
                QList<CellLinePair*> clp1d;
                winCellRecursively(pCell,clp1d);
                clp2d.append(clp1d);
                nTotal += clp1d.count();
                uint iCount = clp1d.count();
                nMax = iCount > nMax ? iCount : nMax;
            }
        }

        cellsOwnableNow = mpController->model()->cellsWinableNow();
    }while(cellsOwnableNow.count() > 0);
}

QLine AILineSelector::considerExtremeMode(QList<Cell *> &cellsOwnableNow)
{
    QLine line;

    if(mbPreCalculated == false){
        mbPreCalculated = true;
        QList< QList< CellLinePair* > > clp2d;
        uint nTotalCells,nMaxAdjacentCells;
        winableCellsInThisTurn(cellsOwnableNow,clp2d,nTotalCells,nMaxAdjacentCells);

        uint nMinCellToFind = 0;

        if(nMaxAdjacentCells >= 2){
            QList<Cell*> cellsToWinLater = mpController->model()->cellsWinableLater();
            sort(cellsToWinLater);

            if(cellsToWinLater.count() > 0){
                line = tryToFindAdjacentLine(cellsToWinLater);

                if(line.isNull()){
                    nMinCellToFind = mnMinCellToFind;
                    line = tryToFindOptimizeLine(cellsToWinLater,nMinCellToFind);
                }
            }
        }

        int nCount = clp2d.count();

        for(int i = nCount - 1; i >= 0; i--){
            QList<CellLinePair *> clp1d = clp2d.at(i);

            for(int j = clp1d.count() - 1; j >= 0; j--){
                clp1d.at(j)->detachLine();
            }
        }

        if(nMinCellToFind > 2){
            applyExtremeTrick(clp2d,nMinCellToFind);
        }

        nCount = clp2d.count();

        for(int i = 0; i < nCount; i++){
            QList<CellLinePair *> clp1d = clp2d.at(i);

            for(int j = 0; j < clp1d.count(); j++){
                CellLinePair *pCLP = clp1d.at(j);
                mLinesPreCalculated.append(pCLP->line());

                delete pCLP;
            }

            clp1d.clear();
        }

        clp2d.clear();
    }

    if(mLinesPreCalculated.count() > 0){
        line = mLinesPreCalculated.at(0);
        mLinesPreCalculated.removeAt(0);

        if(mLinesPreCalculated.count() == 0){
            mbPreCalculated = false;
        }
    }

    return line;
}

bool AILineSelector::applyExtremeTrick(QList< QList< CellLinePair* > > &clp2d,uint nMinCellOwned)
{
    int nCount = clp2d.count();
    bool bTrickApplied = false;

    for(int i = nCount - 1; i >= 0; i-- ){
        QList<CellLinePair*> clp1d = clp2d.at(i);

        if(clp1d.count() >= 2 && nMinCellOwned > 2 && clp1d.at(clp1d.count() - 1)->cell()->lineAddedCount() < 3){
            CellLinePair *pCLP2Last,*pCLP1Last;
            pCLP2Last = clp1d.at(clp1d.count() - 2);
            pCLP1Last = clp1d.at(clp1d.count() - 1);
            clp1d.removeOne(pCLP2Last);
            clp1d.removeOne(pCLP1Last);
            clp2d.replace(i,clp1d);
            QList<CellLinePair*> clpLast;
            clpLast.append(pCLP1Last);
            clp2d.append(clpLast);
            bTrickApplied = true;            
            break;
        }
    }

    if(!bTrickApplied){
        for(int i = nCount - 1; i >= 0; i-- ){
            QList<CellLinePair*> clp1d = clp2d.at(i);

            if(clp1d.count() >= 4 && nMinCellOwned > 4 && clp1d.at(clp1d.count() - 1)->cell()->lineAddedCount() < 2){
                CellLinePair *pCLP4Last,*pCLP3Last,*pCLP2Last,*pCLP1Last;
                pCLP4Last = clp1d.at(clp1d.count() - 4);
                pCLP3Last = clp1d.at(clp1d.count() - 3);
                pCLP2Last = clp1d.at(clp1d.count() - 2);
                pCLP1Last = clp1d.at(clp1d.count() - 1);
                clp1d.removeOne(pCLP4Last);
                clp1d.removeOne(pCLP3Last);
                clp1d.removeOne(pCLP2Last);
                clp1d.removeOne(pCLP1Last);
                clp2d.replace(i,clp1d);
                QList<CellLinePair*> clpLast;
                clpLast.append(pCLP3Last);
                clp2d.append(clpLast);
                bTrickApplied = true;
                break;
            }
        }
    }

    return bTrickApplied;
}

QLine AILineSelector::tryToFindAdjacentLine(QList<Cell *> &cellsAdjacent)
{
    for(int i = 0; i < cellsAdjacent.count(); i++){
        QList<QLine> lines = mpController->model()->getSidesThatAreNotAddedDependingOnAdjacentCells(3,cellsAdjacent.at(i));

        for(int j = 0; j < lines.count(); j++){
            QLine line = lines.at(j);
            Line *pLine = addLine(line);
            QList<Cell*> cellsWinable = mpController->model()->cellsWinableNow();
            removeLine(pLine);

            if(cellsWinable.count() == 0){
                return line;
            }
        }
    }

    return QLine();
}

QLine AILineSelector::tryToFindOptimizeLine(QList<Cell *> &cellsAdjacent, uint &nMinCellWillFillup)
{
    QLine lineChoosed;
    QList<QLine> linesChecked;
    uint nMinCellToFind = mpController->model()->totalCells();;

    for(int i = 0; i < cellsAdjacent.count(); i++){
        QList<QLine> lines = mpController->model()->getSidesThatAreNotAddedDependingOnAdjacentCells(3,cellsAdjacent.at(i));

        for(int j = 0; j < lines.count(); j++){
            QLine line = lines[j];
            bool bDid = false;

            for(int k = 0; k < linesChecked.count(); k++){
                if(Line::isSame(linesChecked.at(k),line)){
                    bDid = true;
                    break;
                }
            }

            if(!bDid){
                linesChecked.append(line);
                continuousSearchForFindingCellOwned(nMinCellToFind,lineChoosed,line);

                if(nMinCellToFind <= nMinCellWillFillup){
                    nMinCellWillFillup = nMinCellToFind;
                    return lineChoosed; // one or zero cell will be owned;
                }
            }
        }
    }

    nMinCellWillFillup = nMinCellToFind;
    return lineChoosed;
}

void AILineSelector::continuousSearchForFindingCellOwned(uint &nMinCellOwned, QLine &nMinLine,QLine startLine)
{
    QList<Line*> linesAdded;
    uint nCount = 0;
    QLine line = startLine;

    do{
        linesAdded.append(addLine(line));
        QList<Cell*> cellsWinable = mpController->model()->cellsWinableNow();
        line = cellsWinable.count() > 0 ? cellsWinable.at(0)->getSidesThatNotAddYet().at(0) : QLine();

        if(!line.isNull()){
            nCount++;
        }
    }
    while(!line.isNull());

    if(nCount < nMinCellOwned){
        nMinCellOwned = nCount;
        nMinLine = startLine;
    }

    for(int k = 0; k < linesAdded.count(); k++){
        removeLine(linesAdded.at(k));
    }

    linesAdded.clear();
}

Line *AILineSelector::addLine(QLine line)
{
    Dot *p1 = mpController->model()->dotsNearToPoint(line.p1()).at(0);
    Dot *p2 = mpController->model()->dotsNearToPoint(line.p2()).at(0);
    Line *pLine = new Line(p1,p2,Game::Player1);
    mpController->model()->addLine(pLine);
    return pLine;
}

void AILineSelector::removeLine(Line *pLine)
{
    mpController->model()->removeLine(pLine);
    delete pLine;
}

void AILineSelector::gameStarted()
{
    initParam();
}

void AILineSelector::initParam()
{
    mnMinCellToFind = 1;
    mbPreCalculated = false;
    mLinesPreCalculated.clear();
}
